package com.frostwire.mp3;

import java.util.Arrays;

public class ID3v2Frame {
    protected static final int DATA_LENGTH_OFFSET = 4;
    private static final int HEADER_LENGTH = 10;
    private static final int ID_OFFSET = 0;
    private static final int ID_LENGTH = 4;
    private static final int FLAGS1_OFFSET = 8;
    private static final int FLAGS2_OFFSET = 9;
    private static final int PRESERVE_TAG_BIT = 6;
    private static final int PRESERVE_FILE_BIT = 5;
    private static final int READ_ONLY_BIT = 4;
    private static final int GROUP_BIT = 6;
    private static final int COMPRESSION_BIT = 3;
    private static final int ENCRYPTION_BIT = 2;
    private static final int UNSYNCHRONISATION_BIT = 1;
    private static final int DATA_LENGTH_INDICATOR_BIT = 0;
    protected String id;
    protected int dataLength = 0;
    protected byte[] data = null;
    private boolean preserveTag = false;
    private boolean preserveFile = false;
    private boolean readOnly = false;
    private boolean group = false;
    private boolean compression = false;
    private boolean encryption = false;
    private boolean unsynchronisation = false;
    private boolean dataLengthIndicator = false;

    public ID3v2Frame(byte[] buffer, int offset) throws InvalidDataException {
        unpackFrame(buffer, offset);
    }

    public ID3v2Frame(String id, byte[] data) {
        this.id = id;
        this.data = data;
        dataLength = data.length;
    }

    protected void unpackFrame(byte[] buffer, int offset) throws InvalidDataException {
        int dataOffset = unpackHeader(buffer, offset);
        sanityCheckUnpackedHeader();
        data = BufferTools.copyBuffer(buffer, dataOffset, dataLength);
    }

    protected int unpackHeader(byte[] buffer, int offset) {
        id = BufferTools.byteBufferToString(buffer, offset + ID_OFFSET, ID_LENGTH);
        unpackDataLength(buffer, offset);
        unpackFlags(buffer, offset);
        return offset + HEADER_LENGTH;
    }

    protected void unpackDataLength(byte[] buffer, int offset) {
        dataLength = BufferTools.unpackInteger(buffer[offset + DATA_LENGTH_OFFSET], buffer[offset + DATA_LENGTH_OFFSET + 1], buffer[offset + DATA_LENGTH_OFFSET + 2], buffer[offset + DATA_LENGTH_OFFSET + 3]);
    }

    private void unpackFlags(byte[] buffer, int offset) {
        preserveTag = BufferTools.checkBit(buffer[offset + FLAGS1_OFFSET], PRESERVE_TAG_BIT);
        preserveFile = BufferTools.checkBit(buffer[offset + FLAGS1_OFFSET], PRESERVE_FILE_BIT);
        readOnly = BufferTools.checkBit(buffer[offset + FLAGS1_OFFSET], READ_ONLY_BIT);
        group = BufferTools.checkBit(buffer[offset + FLAGS2_OFFSET], GROUP_BIT);
        compression = BufferTools.checkBit(buffer[offset + FLAGS2_OFFSET], COMPRESSION_BIT);
        encryption = BufferTools.checkBit(buffer[offset + FLAGS2_OFFSET], ENCRYPTION_BIT);
        unsynchronisation = BufferTools.checkBit(buffer[offset + FLAGS2_OFFSET], UNSYNCHRONISATION_BIT);
        dataLengthIndicator = BufferTools.checkBit(buffer[offset + FLAGS2_OFFSET], DATA_LENGTH_INDICATOR_BIT);
    }

    protected void sanityCheckUnpackedHeader() throws InvalidDataException {
        for (int i = 0; i < id.length(); i++) {
            if (!((id.charAt(i) >= 'A' && id.charAt(i) <= 'Z') || (id.charAt(i) >= '0' && id.charAt(i) <= '9'))) {
                throw new InvalidDataException("Not a valid frame - invalid tag " + id);
            }
        }
    }

    public byte[] toBytes() throws NotSupportedException {
        byte[] bytes = new byte[getLength()];
        packFrame(bytes, 0);
        return bytes;
    }

    public void toBytes(byte[] bytes, int offset) throws NotSupportedException {
        packFrame(bytes, offset);
    }

    public void packFrame(byte[] bytes, int offset) throws NotSupportedException {
        packHeader(bytes, offset);
        BufferTools.copyIntoByteBuffer(data, 0, data.length, bytes, offset + HEADER_LENGTH);
    }

    private void packHeader(byte[] bytes, int i) {
        BufferTools.stringIntoByteBuffer(id, 0, id.length(), bytes, 0);
        BufferTools.copyIntoByteBuffer(packDataLength(), 0, 4, bytes, 4);
        BufferTools.copyIntoByteBuffer(packFlags(), 0, 2, bytes, 8);
    }

    protected byte[] packDataLength() {
        return BufferTools.packInteger(dataLength);
    }

    private byte[] packFlags() {
        byte[] bytes = new byte[2];
        bytes[0] = BufferTools.setBit(bytes[0], PRESERVE_TAG_BIT, preserveTag);
        bytes[0] = BufferTools.setBit(bytes[0], PRESERVE_FILE_BIT, preserveFile);
        bytes[0] = BufferTools.setBit(bytes[0], READ_ONLY_BIT, readOnly);
        bytes[1] = BufferTools.setBit(bytes[1], GROUP_BIT, group);
        bytes[1] = BufferTools.setBit(bytes[1], COMPRESSION_BIT, compression);
        bytes[1] = BufferTools.setBit(bytes[1], ENCRYPTION_BIT, encryption);
        bytes[1] = BufferTools.setBit(bytes[1], UNSYNCHRONISATION_BIT, unsynchronisation);
        bytes[1] = BufferTools.setBit(bytes[1], DATA_LENGTH_INDICATOR_BIT, dataLengthIndicator);
        return bytes;
    }

    public String getId() {
        return id;
    }

    public int getDataLength() {
        return dataLength;
    }

    public int getLength() {
        return dataLength + HEADER_LENGTH;
    }

    public byte[] getData() {
        return data;
    }

    public void setData(byte[] data) {
        this.data = data;
        if (data == null) dataLength = 0;
        else dataLength = data.length;
    }

    public boolean hasDataLengthIndicator() {
        return dataLengthIndicator;
    }

    public boolean hasCompression() {
        return compression;
    }

    public boolean hasEncryption() {
        return encryption;
    }

    public boolean hasGroup() {
        return group;
    }

    public boolean hasPreserveFile() {
        return preserveFile;
    }

    public boolean hasPreserveTag() {
        return preserveTag;
    }

    public boolean isReadOnly() {
        return readOnly;
    }

    public boolean hasUnsynchronisation() {
        return unsynchronisation;
    }

    public boolean equals(Object obj) {
        if (!(obj instanceof ID3v2Frame)) return false;
        if (super.equals(obj)) return true;
        ID3v2Frame other = (ID3v2Frame) obj;
        if (dataLength != other.dataLength) return false;
        if (preserveTag != other.preserveTag) return false;
        if (preserveFile != other.preserveFile) return false;
        if (readOnly != other.readOnly) return false;
        if (group != other.group) return false;
        if (compression != other.compression) return false;
        if (encryption != other.encryption) return false;
        if (unsynchronisation != other.encryption) return false;
        if (dataLengthIndicator != other.dataLengthIndicator) return false;
        if (id == null) {
            if (other.id != null) return false;
        } else if (other.id == null) return false;
        else if (!id.equals(other.id)) return false;
        if (data == null) {
            if (other.data != null) return false;
        } else if (other.data == null) return false;
        else if (!Arrays.equals(data, other.data)) return false;
        return true;
    }
}
